# from pythonds.basic.queue import Queue

import random
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D


class Queue:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def enqueue(self, item):
        self.items.insert(0, item)

    def dequeue(self):
        return self.items.pop()

    def size(self):
        return len(self.items)


class Printer:
    def __init__(self, ppm):
        self.pagerate = ppm
        self.currentTask = None
        self.timeRemaining = 0

    def tick(self):
        if self.currentTask is not None:
            self.timeRemaining = self.timeRemaining - 1
            if self.timeRemaining <= 0:
                self.currentTask = None

    def busy(self):
        if self.currentTask is not None:
            return True
        else:
            return False

    def startNext(self, newtask):
        self.currentTask = newtask
        self.timeRemaining = newtask.getPages() * 60 / self.pagerate


class Task:
    def __init__(self, time):
        self.timestamp = time
        self.pages = random.randrange(1, 21)

    def getStamp(self):
        return self.timestamp

    def getPages(self):
        return self.pages

    def waitTime(self, currenttime):
        return currenttime - self.timestamp


def simulation(numSeconds, pagesPerMinute):

    labprinter = Printer(pagesPerMinute)
    printQueue = Queue()
    waitingtimes = []

    for currentSecond in range(numSeconds):
        if newPrintTask():
            task = Task(currentSecond)
            printQueue.enqueue(task)

        if (not labprinter.busy()) and (not printQueue.isEmpty()):
            nexttask = printQueue.dequeue()
            waitingtimes.append(nexttask.waitTime(currentSecond))
            labprinter.startNext(nexttask)
        # else:
        labprinter.tick()

    averageWait = sum(waitingtimes) / len(waitingtimes)
    # print(waitingtimes)
    # print("Average Wait %6.2f secs %3d tasks remaining." %
    #       (averageWait, printQueue.size()))
    return averageWait, printQueue.size()


def newPrintTask():
    num = random.randrange(1, 181)

    if num == 180:
        # print(num)
        return True
    else:
        return False


def plotresult(ret):

    x_lst, y_lst = diagonal_graph(ret, 50)
    # print(x_lst)
    print("plot result")
    barwidth = 0.5 * (x_lst[1] - x_lst[0])
    plt.bar(x_lst, y_lst, width=barwidth,
            color='blue', linewidth=0.1)
    plt.xlabel("Wait time (s)")
    plt.ylabel("Probability")
    plt.show()


def plotresult2D(ret):
    colors = ['r', 'g', 'b', 'y']

    ppm = [4, 6, 8, 10]

    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    for i in range(len(ppm)):
        x_lst, y_lst = diagonal_graph(ret[i], 50)
        barwidth = 0.5 * (x_lst[1] - x_lst[0])
        ax.bar(x_lst, y_lst, ppm[i], zdir='y', width=barwidth,
               linewidth=0,
               color=colors[i], alpha=0.8)

    ax.set_xlabel("Wait time (s)")
    ax.set_ylabel("PPM")
    ax.set_zlabel("Probability")
    plt.show()


def diagonal_graph(y_data, barnum=10):
    y_min = min(y_data)
    y_max = max(y_data)
    y_lst = [0] * barnum

    delta_prob = 1 / len(y_data)
    delta = (y_max - y_min) / (barnum - 1)

    for y in y_data:
        y_lst[int((y - y_min) / delta)] += delta_prob
    x_lst = [0] * barnum
    for i in range(barnum):
        x_lst[i] = (y_min + (i + 0.25) * delta)
    return x_lst, y_lst


# simulation(3600, 5)
def Test1():
    averageWaits = []
    remtask = []

    for i in range(10000):
        x, y = simulation(3600, 5)
        averageWaits.append(x)
        remtask.append(y)

    # print(averageWaits)
    plotresult(averageWaits)


def Test2D():

    ret = []

    ppm = [4, 6, 8, 10]
    for p in ppm:
        averageWaits = []
        remtask = []
        for i in range(2000):
            x, y = simulation(3600, p)
            averageWaits.append(x)
            remtask.append(y)
        ret.append(averageWaits)

    plotresult2D(ret)


if __name__ == "__main__":
    # Test1()
    Test2D()
